﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Numerics;
using ParserGenerator.CharacterMapping;
using ParserGenerator.Collections;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.Input;
using ParserGenerator.ParserConstruction;

namespace ParserGenerator.SyntacticAnalysis
{
    partial class GrammarFragment
    {
        public static GrammarFragment LoadFromFile(string path)
        {
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            using (StreamReader stream = new StreamReader(path))
            {
                SourceReader reader = SourceReader.FromStream(path, stream);
                ProgramDescriptionParser parser = new ProgramDescriptionParser(reader);
                ProgramDescription ast = parser.ParseGrammarModule();
                return ast.AsGrammar.Body;
            }
        }

        public static GrammarFragment LoadFromFile(string path, bool recursive)
        {
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            GrammarFragment frag = LoadFromFile(path);
            if (!recursive)
                return frag;
            path = Path.GetFullPath(path);
            path = Path.GetDirectoryName(path);
            frag = frag.Expand(path);
            return frag;
        }

        public GrammarFragment Expand(string path)
        {
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            var directives = new List<Directive>();
            var types = new List<TypeDeclarator>();
            var syntacticDeclarators = new List<SyntacticDeclarator>();
            var lexicalDeclarators = new List<LexicalDeclarator>();
            var loaded = new HashSet<string>();
            var stack = new Stack<KeyValuePair<GrammarFragment, string>>();
            stack.Push(new KeyValuePair<GrammarFragment, string>(this, path));
            while (stack.Count > 0)
            {
                var entry = stack.Pop();
                var frag = entry.Key;
                var dir = entry.Value;
                var submodules = new List<IncludeDeclarator>();
                foreach (Directive item in frag.Directives.Items)
                    if (item.IsInclude)
                        submodules.AddRange(item.AsInclude.Declaration.Items);
                    else
                        directives.Add(item);
                types.AddRange(frag.Types.Declarators);
                syntacticDeclarators.AddRange(frag.SyntacticDeclaration.Declarators);
                lexicalDeclarators.AddRange(frag.LexicalDeclaration.Declarators);
                var entries = new List<KeyValuePair<GrammarFragment, string>>();
                foreach (IncludeDeclarator submodule in submodules)
                {
                    string filePath;
                    if (submodule.IsNamed)
                    {
                        Identifier name = Identifier.New(submodule.AsNamed.Name, submodule.Context);
                        string fileName = name.Name + ".g";
                        filePath = Path.Combine(dir, fileName);
                    }
                    else
                    {
                        StringLiteral name = submodule.AsLiteral.Value;
                        string fileName = submodule.AsLiteral.Value.Value;
                        filePath = Path.Combine(dir, fileName);
                    }
                    filePath = Path.GetFullPath(filePath);
                    if (!File.Exists(filePath))
                    {
                        string message = string.Format(SR.GrammarSubmoduleNotFound, filePath);
                        GrammarErrorException e = new GrammarErrorException(message);
                        e.AddLocation(Context);
                        e.AddLocation(submodule.Context);
                        throw e;
                    }
                    if (!loaded.Contains(filePath))
                    {
                        GrammarFragment sub = LoadFromFile(filePath);
                        string dirPath = Path.GetDirectoryName(filePath);
                        entries.Add(new KeyValuePair<GrammarFragment, string>(sub, dirPath));
                        loaded.Add(filePath);
                    }
                }
                for (int i = entries.Count - 1; i >= 0; i--)
                    stack.Push(entries[i]);
            }
            var d = Directives.WithItems(Sequence.New(directives));
            var t = Types.WithDeclarators(Sequence.New(types));
            var s = SyntacticDeclaration.WithDeclarators(Sequence.New(syntacticDeclarators));
            var l = LexicalDeclaration.WithDeclarators(Sequence.New(lexicalDeclarators));
            return New(d, t, s, l, Context);
        }
    }

    partial class GrammarDeclaration
    {
        public static string GrammarNamespaceKey { get; } = "pg.GrammarNamespace";
        public static string GrammarSourceFileKey { get; } = "pg.GrammarSourceFile";
        public static string RootNamespaceKey { get; } = "pg.RootNamespace";
        public static string ExceptionsNamespaceKey { get; } = "pg.ExceptionsNamespace";
        public static string LexicalAnalysisNamespaceKey { get; } = "pg.LexicalAnalysisNamespace";
        public static string SyntacticAnalysisNamespaceKey { get; } = "pg.SyntacticAnalysisNamespace";
        public static string OutputNamespaceKey { get; } = "pg.OutputNamespace";
        public static string SourceContextNamespaceKey { get; } = "pg.SourceContextNamespace";
        public static string SourceContextClassNameKey { get; } = "pg.SourceContextClassName";
        public static string SourceContextClassNameDefault { get; } = "SourceContext";
        public static string SourceContextFieldNameKey { get; } = "pg.SourceContextFieldName";
        public static string SourceContextFieldNameDefault { get; } = "context";
        public static string UseSourceContextFieldKey { get; } = "pg.UseSourceContextField";
        public static bool UseSourceContextFieldDefault { get; } = true;
        public static string IndentedWriterNamespaceKey { get; } = "pg.IndentedWriterNamespace";
        public static string IndentedWriterClassNameKey { get; } = "pg.IndentedWriterClassName";
        public static string IndentedWriterClassNameDefault { get; } = "IndentedWriter";
        public static string DefaultScannerStartNameKey { get; } = "pg.DefaultScannerStartName";
        public static string DefaultScannerStartNameDefault { get; } = "DEFAULT";
        public static string AllowAutoDefineLiteralTokensKey { get; } = "pg.AllowAutoDefineLiteralTokens";
        public static bool AllowAutoDefineLiteralTokensDefault { get; } = true;
        public static string UsePythonLikeIndentationKey { get; } = "pg.UsePythonLikeIndentation";
        public static bool UsePythonLikeIndentationDefault { get; } = true;
        public static string UseLiteralFirstResolutionsKey { get; } = "pg.UseLiteralFirstResolutions";
        public static bool UseLiteralFirstResolutionsDefault { get; } = true;
        public static string CharSetUniversalKey { get; } = "pg.CharSetUniversal";
        public static string CharSetUniversalDefault { get; } = "char32";
        public static string ParserAutomatonTypeKey { get; } = "pg.ParserAutomatonType";
        public static string ParserAutomatonTypeDefault { get; } = "IELR";
        public static string UseDefaultParserReductionsKey { get; } = "pg.UseDefaultParserReductions";
        public static bool UseDefaultParserReductionsDefault { get; } = false;
        public static string EndMarkerTokenNameKey { get; } = "pg.EndMarkerTokenName";
        public static string EndMarkerTokenNameDefault { get; } = "None";
        public static string EndMarkerTokenTextKey { get; } = "pg.EndMarkerTokenText";
        public static string EndMarkerTokenTextDefault { get; } = "end marker";
        public static string IndentTokenNameKey { get; } = "pg.IndentTokenName";
        public static string IndentTokenNameDefault { get; } = "Indent";
        public static string IndentTokenTextKey { get; } = "pg.IndentTokenText";
        public static string IndentTokenTextDefault { get; } = "indent";
        public static string UnindentTokenNameKey { get; } = "pg.UnindentTokenName";
        public static string UnindentTokenNameDefault { get; } = "Unindent";
        public static string UnindentTokenTextKey { get; } = "pg.UnindentTokenText";
        public static string UnindentTokenTextDefault { get; } = "unindent";
        public static string NewLineTokenNameKey { get; } = "pg.NewLineTokenName";
        public static string NewLineTokenNameDefault { get; } = "NewLine";
        public static string NewLineTokenTextKey { get; } = "pg.NewLineTokenText";
        public static string NewLineTokenTextDefault { get; } = "new line";
        public static string StringResourcesNamespaceKey { get; } = "pg.StringResourcesNamespace";
        public static string StringResourcesClassNameKey { get; } = "pg.StringResourcesClassName";
        public static string StringResourcesClassNameDefault { get; } = "SR";
        public static string UseAssemblyStringResourcesKey { get; } = "pg.UseAssemblyStringResources";
        public static bool UseAssemblyStringResourcesDefault { get; } = true;
        public static string UseSyntaxTreeNodeTypesKey { get; } = "pg.UseSyntaxTreeNodeTypes";
        public static bool UseSyntaxTreeNodeTypesDefault { get; } = true;
        public static string SyntaxTreeNodeInterfaceNameKey { get; } = "pg.SyntaxTreeNode.InterfaceName";
        public static string DefaultExceptionsNamespaceSuffix { get; } = "Exceptions";
        public static string DefaultLexicalAnalysisNamespaceSuffix { get; } = "LexicalAnalysis";
        public static string DefaultSyntacticAnalysisNamespaceSuffix { get; } = "SyntacticAnalysis";
        public static string DefaultOutputNamespaceSuffix { get; } = "Output";
        public static string SyntaxTreeNodeInterfaceNameDefault { get; } = "ISyntaxTreeNode";
        public static string SyntaxTreeNodeInterfaceIsInternalKey { get; } = "pg.SyntaxTreeNode.InterfaceIsInternal";
        public static bool SyntaxTreeNodeInterfaceIsInternalDefault { get; } = false;

        public GrammarDeclaration Expand(string path)
        {
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            GrammarFragment frag = GrammarFragment.New(Directives, Types, SyntacticDeclaration, LexicalDeclaration);
            GrammarFragment f = frag.Expand(path);
            return New(Name, Arguments, f.Directives, f.Types, f.SyntacticDeclaration, f.LexicalDeclaration, Decorators, Context);
        }

        public GrammarDeclaration Expand()
        {
            if (Context == null || !File.Exists(Context.Document.FullName))
            {
                GrammarErrorException e = new GrammarErrorException(SR.GrammarNoFilePathCanNotExpand);
                e.AddLocation(Context);
                throw e;
            }
            string path = Context.Document.FullName;
            path = Path.GetFullPath(path);
            path = Path.GetDirectoryName(path);
            return Expand(path);
        }

        public static GrammarDeclaration LoadFromFile(string path)
        {
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            GrammarFragment f = GrammarFragment.LoadFromFile(path);
            string filePath = Path.GetFullPath(path);
            string fileName = Path.GetFileNameWithoutExtension(path);
            Identifier id = Identifier.New(fileName);
            if (!id.IsDottedName)
            {
                string message = string.Format(SR.GrammarFileNameNotDottedNameCanNotSynthesize, fileName.AsSingleQuote());
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(f.Context);
                throw e;
            }
            List<Decorator> decorators = new List<Decorator>();
            var names = Sequence.New(id.Items);
            if (names.Count > 1)
            {
                var ns = Sequence.Create(Identifier.New(RootNamespaceKey));
                var nsStr = string.Join(".", names.RemoveAt(names.Count - 1));
                var nsStrLit = StringLiteral.New(nsStr, false);
                var nsArg = Argument.NewPositional(Expression.NewString(nsStrLit));
                var nsArgs = ArgumentList.New(Sequence.Create(nsArg));
                var nsDeco = Decorator.NewDottedNameCall(ns, nsArgs);
                decorators.Add(nsDeco);
            }
            var source = Sequence.Create(Identifier.New(GrammarSourceFileKey));
            var sourcePath = StringLiteral.New(filePath, false);
            var sourceArg = Argument.NewPositional(Expression.NewString(sourcePath));
            var sourceArgs = ArgumentList.New(Sequence.Create(sourceArg));
            var sourceDeco = Decorator.NewDottedNameCall(source, sourceArgs);
            decorators.Add(sourceDeco);
            var n = names[names.Count - 1];
            var a = ArgumentList.New(Sequence.GetEmpty<Argument>());
            var d = DecoratorList.New(Sequence.New(decorators));
            return New(n, a, f.Directives, f.Types, f.SyntacticDeclaration, f.LexicalDeclaration, d, f.Context);
        }

        public static GrammarDeclaration LoadFromFile(string path, bool recursive)
        {
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            GrammarDeclaration g = LoadFromFile(path);
            if (!recursive)
                return g;
            path = Path.GetFullPath(path);
            path = Path.GetDirectoryName(path);
            g = g.Expand(path);
            return g;
        }

        private bool TryGetRootNamespace(out string value)
        {
            if (Directives.TryFindOption(RootNamespaceKey, out value))
                return true;
            if (Decorators.TryGetSingleValue(RootNamespaceKey, out value))
                return true;
            string ns;
            if (!Decorators.TryGetSingleValue(GrammarNamespaceKey, out ns))
                return false;
            Identifier id = Identifier.New(ns);
            id.CheckDottedName();
            value = id.Items.First().Name;
            return true;
        }

        private string _rootNamespace;
        public string RootNamespace
        {
            get
            {
                if (_rootNamespace == null)
                {
                    string value;
                    if (TryGetRootNamespace(out value))
                        _rootNamespace = value;
                    else
                    {
                        string message = string.Format(SR.CanNotDetermineRootNamespace, RootNamespaceKey.AsSingleQuote());
                        RuntimeErrorException e = new RuntimeErrorException(message);
                        e.AddLocation(Context);
                        e.AddLocation(Directives.Context);
                        e.AddLocation(Decorators.Context);
                        throw e;
                    }
                }
                return _rootNamespace;
            }
        }

        private string GetNamespace(string key, string suffix)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (suffix == null)
                throw new ArgumentNullException(nameof(suffix));
            string value;
            if (Directives.TryFindOption(key, out value))
                return value;
            if (Decorators.TryGetSingleValue(key, out value))
                return value;
            value = RootNamespace + "." + suffix;
            return value;
        }

        public T GetOptionValue<T>(string key, T defaultValue)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            T value;
            if (Directives.TryFindOption(key, out value))
                return value;
            if (Decorators.TryGetSingleValue(key, out value))
                return value;
            value = defaultValue;
            return value;
        }

        public bool TryGetOptionValue<T>(string key, out T value)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            if (Directives.TryFindOption(key, out value))
                return true;
            if (Decorators.TryGetSingleValue(key, out value))
                return true;
            value = default(T);
            return false;
        }

        private static TypeExpression CreateTypeReference(string fullName)
        {
            if (fullName == null)
                throw new ArgumentNullException(nameof(fullName));
            Identifier id = Identifier.New(fullName);
            id.CheckDottedName();
            TypeExpression n = null;
            foreach (Identifier item in id.Items)
                if (n == null)
                    n = TypeExpression.NewGlobalNameReference(item);
                else
                    n = TypeExpression.NewAccess(n, item);
            return n;
        }

        private string _exceptionsNamespace;
        public string ExceptionsNamespace
        {
            get
            {
                if (_exceptionsNamespace == null)
                    _exceptionsNamespace = GetNamespace(ExceptionsNamespaceKey, DefaultExceptionsNamespaceSuffix);
                return _exceptionsNamespace;
            }
        }

        private string _lexicalAnalysisNamespace;
        public string LexicalAnalysisNamespace
        {
            get
            {
                if (_lexicalAnalysisNamespace == null)
                    _lexicalAnalysisNamespace = GetNamespace(LexicalAnalysisNamespaceKey, DefaultLexicalAnalysisNamespaceSuffix);
                return _lexicalAnalysisNamespace;
            }
        }

        private string _syntacticAnalysisNamespace;
        public string SyntacticAnalysisNamespace
        {
            get
            {
                if (_syntacticAnalysisNamespace == null)
                    _syntacticAnalysisNamespace = GetNamespace(SyntacticAnalysisNamespaceKey, DefaultSyntacticAnalysisNamespaceSuffix);
                return _syntacticAnalysisNamespace;
            }
        }

        private string _outputNamespace;
        public string OutputNamespace
        {
            get
            {
                if (_outputNamespace == null)
                    _outputNamespace = GetNamespace(OutputNamespaceKey, DefaultOutputNamespaceSuffix);
                return _outputNamespace;
            }
        }

        private TypeExpression _sourceContextReference;
        public TypeExpression SourceContextReference
        {
            get
            {
                if (_sourceContextReference == null)
                {
                    string ns = GetOptionValue(SourceContextNamespaceKey, LexicalAnalysisNamespace);
                    string cn = GetOptionValue(SourceContextClassNameKey, SourceContextClassNameDefault);
                    _sourceContextReference = TypeExpression.NewNullable(CreateTypeReference(ns + "." + cn));
                }
                return _sourceContextReference;
            }
        }

        private Identifier _sourceContextFieldName;
        public Identifier SourceContextFieldName
        {
            get
            {
                if (_sourceContextFieldName == null)
                    _sourceContextFieldName = Identifier.New(GetOptionValue(SourceContextFieldNameKey, SourceContextFieldNameDefault));
                return _sourceContextFieldName;
            }
        }

        private StructFieldDeclarator _sourceContextField;
        public StructFieldDeclarator SourceConextField
        {
            get
            {
                if (_sourceContextField == null)
                    _sourceContextField = StructFieldDeclarator.New(SourceContextFieldName, true, SourceContextReference);
                return _sourceContextField;
            }
        }

        private string _indentedWriterNamespace;
        public string IndentedWriterNamespace
        {
            get
            {
                if (_indentedWriterNamespace == null)
                    _indentedWriterNamespace = GetOptionValue(IndentedWriterNamespaceKey, OutputNamespace);
                return _indentedWriterNamespace;
            }
        }

        private string _indentedWriterClassName;
        public string IndentedWriterClassName
        {
            get
            {
                if (_indentedWriterClassName == null)
                    _indentedWriterClassName = GetOptionValue(IndentedWriterClassNameKey, IndentedWriterClassNameDefault);
                return _indentedWriterClassName;
            }
        }

        private TypeExpression _indentedWriterClassReference;
        public TypeExpression IndentedWriterClassReference
        {
            get
            {
                if (_indentedWriterClassReference == null)
                    _indentedWriterClassReference = CreateTypeReference(IndentedWriterNamespace + "." + IndentedWriterClassName);
                return _indentedWriterClassReference;
            }
        }

        private bool? _useSourceContextField;
        public bool UseSourceContextField
        {
            get
            {
                if (_useSourceContextField == null)
                    _useSourceContextField = GetOptionValue(UseSourceContextFieldKey, UseSourceContextFieldDefault);
                return _useSourceContextField.Value;
            }
        }

        private string _defaultScannerStartName;
        public string DefaultScannerStartName
        {
            get
            {
                if (_defaultScannerStartName == null)
                    _defaultScannerStartName = GetOptionValue(DefaultScannerStartNameKey, DefaultScannerStartNameDefault);
                return _defaultScannerStartName;
            }
        }

        private bool? _allowAutoDefineLiteralTokens;
        public bool AllowAutoDefineLiteralTokens
        {
            get
            {
                if (_allowAutoDefineLiteralTokens == null)
                    _allowAutoDefineLiteralTokens = GetOptionValue(AllowAutoDefineLiteralTokensKey, AllowAutoDefineLiteralTokensDefault);
                return _allowAutoDefineLiteralTokens.Value;
            }
        }

        private bool? _usePythonLikeIndentation;
        public bool UsePythonLikeIndentation
        {
            get
            {
                if (_usePythonLikeIndentation == null)
                    _usePythonLikeIndentation = GetOptionValue(UsePythonLikeIndentationKey, UsePythonLikeIndentationDefault);
                return _usePythonLikeIndentation.Value;
            }
        }

        private bool? _useLiteralFirstResolutions;
        public bool UseLiteralFirstResolutions
        {
            get
            {
                if (_useLiteralFirstResolutions == null)
                    _useLiteralFirstResolutions = GetOptionValue(UseLiteralFirstResolutionsKey, UseLiteralFirstResolutionsDefault);
                return _useLiteralFirstResolutions.Value;
            }
        }

        private CharInterval? _charSetUniversal;
        public CharInterval CharSetUniversal
        {
            get
            {
                if (_charSetUniversal == null)
                {
                    string charSet = GetOptionValue(CharSetUniversalKey, CharSetUniversalDefault);
                    switch (charSet)
                    {
                        case "ascii":
                            _charSetUniversal = CharInterval.New(0, 0x7F);
                            break;
                        case "char8":
                            _charSetUniversal = CharInterval.New(0, 0xFF);
                            break;
                        case "char16":
                            _charSetUniversal = CharInterval.New(0, 0xFFFF);
                            break;
                        case "char32":
                            _charSetUniversal = CharInterval.Universal;
                            break;
                        default:
                            GrammarErrorException e = new GrammarErrorException(SR.CharSetInvalid);
                            Expression v;
                            if (TryGetOptionValue(CharSetUniversalKey, out v))
                                e.AddLocation(v.Context);
                            throw e;
                    }
                }
                return _charSetUniversal.Value;
            }
        }

        private ParserAutomatonType? _parserAutomatonType;
        public ParserAutomatonType ParserAutomatonType
        {
            get
            {
                if (_parserAutomatonType == null)
                {
                    string lrType = GetOptionValue(ParserAutomatonTypeKey, ParserAutomatonTypeDefault);
                    switch (lrType)
                    {
                        case "LALR":
                            _parserAutomatonType = ParserAutomatonType.LALR;
                            break;
                        case "IELR":
                            _parserAutomatonType = ParserAutomatonType.IELR;
                            break;
                        case "PSLR":
                            _parserAutomatonType = ParserAutomatonType.PSLR;
                            break;
                        case "LR":
                            _parserAutomatonType = ParserAutomatonType.LR;
                            break;
                        default:
                            GrammarErrorException e = new GrammarErrorException(SR.ShiftReduceAutomatonTypeInvalid);
                            Expression v;
                            if (TryGetOptionValue(ParserAutomatonTypeKey, out v))
                                e.AddLocation(v.Context);
                            throw e;
                    }
                }
                return _parserAutomatonType.Value;
            }
        }

        private bool? _useDefaultParserReductions;
        public bool UseDefaultParserReductions
        {
            get
            {
                if (_useDefaultParserReductions == null)
                    _useDefaultParserReductions = GetOptionValue(UseDefaultParserReductionsKey, UseDefaultParserReductionsDefault);
                return _useDefaultParserReductions.Value;
            }
        }

        private string _endMarkerTokenName;
        public string EndMarkerTokenName
        {
            get
            {
                if (_endMarkerTokenName == null)
                    _endMarkerTokenName = GetOptionValue(EndMarkerTokenNameKey, EndMarkerTokenNameDefault);
                return _endMarkerTokenName;
            }
        }

        private string _endMarkerTokenText;
        public string EndMarkerTokenText
        {
            get
            {
                if (_endMarkerTokenText == null)
                    _endMarkerTokenText = GetOptionValue(EndMarkerTokenTextKey, EndMarkerTokenTextDefault);
                return _endMarkerTokenText;
            }
        }

        private string _indentTokenName;
        public string IndentTokenName
        {
            get
            {
                if (_indentTokenName == null)
                    _indentTokenName = GetOptionValue(IndentTokenNameKey, IndentTokenNameDefault);
                return _indentTokenName;
            }
        }

        private string _indentTokenText;
        public string IndentTokenText
        {
            get
            {
                if (_indentTokenText == null)
                    _indentTokenText = GetOptionValue(IndentTokenTextKey, IndentTokenTextDefault);
                return _indentTokenText;
            }
        }

        private string _unindentTokenName;
        public string UnindentTokenName
        {
            get
            {
                if (_unindentTokenName == null)
                    _unindentTokenName = GetOptionValue(UnindentTokenNameKey, UnindentTokenNameDefault);
                return _unindentTokenName;
            }
        }

        private string _unindentTokenText;
        public string UnindentTokenText
        {
            get
            {
                if (_unindentTokenText == null)
                    _unindentTokenText = GetOptionValue(UnindentTokenTextKey, UnindentTokenTextDefault);
                return _unindentTokenText;
            }
        }

        private string _newLineTokenName;
        public string NewLineTokenName
        {
            get
            {
                if (_newLineTokenName == null)
                    _newLineTokenName = GetOptionValue(NewLineTokenNameKey, NewLineTokenNameDefault);
                return _newLineTokenName;
            }
        }

        private string _newLineTokenText;
        public string NewLineTokenText
        {
            get
            {
                if (_newLineTokenText == null)
                    _newLineTokenText = GetOptionValue(NewLineTokenTextKey, NewLineTokenTextDefault);
                return _newLineTokenText;
            }
        }

        private TypeExpression _stringResourcesClassReference;
        public TypeExpression StringResourcesClassReference
        {
            get
            {
                if (_stringResourcesClassReference == null)
                {
                    string ns = GetOptionValue(StringResourcesNamespaceKey, RootNamespace);
                    string cn = GetOptionValue(StringResourcesClassNameKey, StringResourcesClassNameDefault);
                    _stringResourcesClassReference = CreateTypeReference(ns + "." + cn);
                }
                return _stringResourcesClassReference;
            }
        }

        private bool? _useAssemblyStringResources;
        public bool UseAssemblyStringResources
        {
            get
            {
                if (_useAssemblyStringResources == null)
                    _useAssemblyStringResources = GetOptionValue(UseAssemblyStringResourcesKey, UseAssemblyStringResourcesDefault);
                return _useAssemblyStringResources.Value;
            }
        }

        private bool? _useSyntaxTreeNodeTypes;
        public bool UseSyntaxTreeNodeTypes
        {
            get
            {
                if (_useSyntaxTreeNodeTypes == null)
                    _useSyntaxTreeNodeTypes = GetOptionValue(UseSyntaxTreeNodeTypesKey, UseSyntaxTreeNodeTypesDefault);
                return _useSyntaxTreeNodeTypes.Value;
            }
        }

        private string _syntaxTreeNodeInterfaceName;
        public string SyntaxTreeNodeInterfaceName
        {
            get
            {
                if (_syntaxTreeNodeInterfaceName == null)
                    _syntaxTreeNodeInterfaceName = GetOptionValue(SyntaxTreeNodeInterfaceNameKey, SyntaxTreeNodeInterfaceNameDefault);
                return _syntaxTreeNodeInterfaceName;
            }
        }

        private TypeExpression _syntaxTreeNodeInterfaceReference;
        public TypeExpression SyntaxTreeNodeInterfaceReference
        {
            get
            {
                if (_syntaxTreeNodeInterfaceReference == null)
                    _syntaxTreeNodeInterfaceReference = CreateTypeReference(SyntacticAnalysisNamespace + "." + SyntaxTreeNodeInterfaceName);
                return _syntaxTreeNodeInterfaceReference;
            }
        }

        private bool? _syntaxTreeNodeInterfaceIsInternal;
        public bool SyntaxTreeNodeInterfaceIsInternal
        {
            get
            {
                if (_syntaxTreeNodeInterfaceIsInternal == null)
                    _syntaxTreeNodeInterfaceIsInternal = GetOptionValue(SyntaxTreeNodeInterfaceIsInternalKey, SyntaxTreeNodeInterfaceIsInternalDefault);
                return _syntaxTreeNodeInterfaceIsInternal.Value;
            }
        }

        private InterfaceDeclarator _syntaxTreeNodeInterfaceDeclarator;
        public InterfaceDeclarator SyntaxTreeNodeInterfaceDeclarator
        {
            get
            {
                if (_syntaxTreeNodeInterfaceDeclarator == null)
                {
                    bool isInternal = SyntaxTreeNodeInterfaceIsInternal;
                    Identifier name = Identifier.New(SyntaxTreeNodeInterfaceName);
                    TypeExpression list = CreateTypeReference("System.Collections.Generic.IReadOnlyList");
                    Sequence<TypeExpression> listArgs = Sequence.Create(SyntaxTreeNodeInterfaceReference);
                    TypeExpression baseRef = TypeExpression.NewBoundedGeneric(list, listArgs);
                    Sequence<TypeExpression> bases = Sequence.Create(baseRef);
                    var members = Sequence.GetEmpty<InterfaceMemberDeclarator>();
                    if (UseSourceContextField)
                    {
                        var contextName = SourceContextFieldName.ToUpperCase();
                        var context = InterfaceMemberDeclarator.NewProperty(contextName, true, SourceContextReference);
                        members = Sequence.Append(members, context);
                    }
                    _syntaxTreeNodeInterfaceDeclarator = InterfaceDeclarator.New(isInternal, name, bases, members);
                }
                return _syntaxTreeNodeInterfaceDeclarator;
            }
        }

        private ReadOnlyDictionary<string, TypeDeclarator> _syntaxTreeNodeTypes;
        public ReadOnlyDictionary<string, TypeDeclarator> SyntaxTreeNodeTypes
        {
            get
            {
                if (_syntaxTreeNodeTypes == null)
                {
                    var dict = new Dictionary<string, TypeDeclarator>();
                    foreach (TypeDeclarator decl in Types.Declarators)
                    {
                        TypeDeclarator d;
                        if (dict.TryGetValue(decl.Name.Name, out d))
                        {
                            string message = string.Format(SR.SyntaxTreeNodeTypeDefined, decl.Name.Name.AsSingleQuote());
                            NameErrorException e = new NameErrorException(message);
                            e.AddLocation(d.Name.Context);
                            e.AddLocation(decl.Name.Context);
                            throw e;
                        }
                        dict.Add(decl.Name.Name, decl);
                    }
                    _syntaxTreeNodeTypes = dict.AsReadOnly();
                }
                return _syntaxTreeNodeTypes;
            }
        }
    }

    partial class Expression
    {
        public bool TryGetDottedName(out string result)
        {
            Expression n = this;
            List<Identifier> names = new List<Identifier>();
            while (n.IsAccess)
            {
                names.Add(n.AsAccess.Member);
                n = n.AsAccess.Operand;
            }
            if (n.IsNameReference)
            {
                names.Add(n.AsNameReference.Name);
                names.Reverse();
                string name = string.Join(".", names);
                Identifier id = Identifier.New(name);
                if (id.IsDottedName)
                {
                    result = id.Name;
                    return true;
                }
            }
            result = default(string);
            return false;
        }

        public bool TryGetValue<T>(out T result)
        {
            string dottedName;
            if (typeof(T) == typeof(Expression))
                result = (T)(object)this;
            else if (typeof(T) == typeof(bool) && IsBoolean)
                result = (T)(object)AsBoolean.Value.Value;
            else if (typeof(T) == typeof(BigInteger) && IsInteger)
                result = (T)(object)AsInteger.Value.Value;
            else if (typeof(T) == typeof(int) && IsInteger)
                result = (T)(object)(int)AsInteger.Value.Value;
            else if (typeof(T) == typeof(double) && IsFloat)
                result = (T)(object)AsFloat.Value.Value;
            else if (typeof(T) == typeof(string) && IsNameReference)
                result = (T)(object)AsNameReference.Name.Name;
            else if (typeof(T) == typeof(string) && IsString)
                result = (T)(object)AsString.Value.Value;
            else if (typeof(T) == typeof(string) && IsQuoteString)
                result = (T)(object)AsQuoteString.Value;
            else if (typeof(T) == typeof(string) && TryGetDottedName(out dottedName))
                result = (T)(object)dottedName;
            else if (typeof(T) == typeof(ReadOnlyCollection<byte>) && IsByteString)
                result = (T)(object)AsByteString.Value.Value;
            else if (typeof(T) == typeof(ReadOnlyCollection<byte>) && IsQuoteByteString)
                result = (T)(object)AsQuoteByteString.Value.Value;
            else
            {
                result = default(T);
                return false;
            }
            return true;
        }

        public T GetValue<T>()
        {
            T value;
            if (TryGetValue(out value))
                return value;
            string message = string.Format(SR.ValueMustBeOfType, typeof(T).Name.AsSingleQuote());
            TypeErrorException e = new TypeErrorException(message);
            e.AddLocation(Context);
            throw e;
        }
    }

    partial class DirectiveDeclaration
    {
        [NonSerialized]
        private ReadOnlyDictionary<string, Expression> _options;
        public ReadOnlyDictionary<string, Expression> Options
        {
            get
            {
                if (_options == null)
                {
                    var dict = new Dictionary<string, Expression>();
                    foreach (Directive item in Items)
                    {
                        if (!item.IsDefine)
                            continue;
                        var d = item.AsDefine.Declaration;
                        Identifier id = Identifier.New(d.Name);
                        id.CheckDottedName();
                        Expression v;
                        if (dict.TryGetValue(id.Name, out v))
                        {
                            string message = string.Format(SR.GrammarOptionDefined, id.Name.AsSingleQuote());
                            GrammarErrorException e = new GrammarErrorException(message);
                            e.AddLocation(item.Context);
                            e.AddLocation(d.Context);
                            e.AddLocation(d.Value.Context);
                            e.AddLocation(v.Context);
                            throw e;
                        }
                        dict.Add(id.Name, d.Value);
                    }
                    _options = dict.AsReadOnly();
                }
                return _options;
            }
        }

        public Expression FindOption(string key)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            Identifier id = Identifier.New(key);
            id.CheckDottedName();
            Expression v;
            if (Options.TryGetValue(key, out v))
                return v;
            string message = string.Format(SR.GrammarOptionNotDefined, id.Name.AsSingleQuote());
            GrammarErrorException e = new GrammarErrorException(message);
            e.AddLocation(Context);
            throw e;
        }

        public bool TryFindOption<T>(string key, out T result)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            Identifier id = Identifier.New(key);
            id.CheckDottedName();
            Expression v;
            if (!Options.TryGetValue(key, out v))
            {
                result = default(T);
                return false;
            }
            result = v.GetValue<T>();
            return true;
        }

        public T FindOption<T>(string key, T defaultValue)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            T value;
            if (TryFindOption(key, out value))
                return value;
            else
                return defaultValue;
        }
    }

    partial class DecoratorList
    {
        [NonSerialized]
        private ReadOnlyDictionary<string, ReadOnlyCollection<Decorator>> _table;
        public ReadOnlyDictionary<string, ReadOnlyCollection<Decorator>> Table
        {
            get
            {
                if (_table == null)
                {
                    var dict = new Dictionary<string, List<Decorator>>();
                    foreach (Decorator item in Items)
                    {
                        Identifier id;
                        if (item.IsDottedName)
                            id = Identifier.New(item.AsDottedName.Names);
                        else
                            id = Identifier.New(item.AsDottedNameCall.Names);
                        id.CheckDottedName();
                        dict.AddValueItem(id.Name, item);
                    }
                    _table = dict.AsReadOnly();
                }
                return _table;
            }
        }

        public bool TryGetSingle(string key, out Decorator value)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            Identifier id = Identifier.New(key);
            id.CheckDottedName();
            ReadOnlyCollection<Decorator> list;
            if (!Table.TryGetValue(id.Name, out list))
            {
                value = default(Decorator);
                return false;
            }
            if (list.Count > 1)
            {
                string message = string.Format(SR.DecoratorDefinedMoreThanOnce, id.Name.AsSingleQuote());
                RuntimeErrorException e = new RuntimeErrorException(message);
                foreach (Decorator item in list)
                    e.AddLocation(item.Context);
                throw e;
            }
            value = list[0];
            return true;
        }

        public Decorator GetSingle(string key)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            Decorator value;
            if (TryGetSingle(key, out value))
                return value;
            string message = string.Format(SR.DecoratorNotDefined, key.AsSingleQuote());
            RuntimeErrorException e = new RuntimeErrorException(message);
            throw e;
        }

        private static bool IsSingleValued(Decorator n)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (!n.IsDottedNameCall)
                return false;
            ArgumentList args = n.AsDottedNameCall.Arguments;
            if (args.Items.Count != 1)
                return false;
            Argument arg = args.Items[0];
            if (!arg.IsPositional)
                return false;
            return true;
        }

        public bool TryGetSingleValue<T>(string key, out T value)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            Decorator d;
            if (!TryGetSingle(key, out d))
            {
                value = default(T);
                return false;
            }
            if (!IsSingleValued(d))
            {
                string message = string.Format(SR.DecoratorNotSingleValued, key.AsSingleQuote());
                RuntimeErrorException e = new RuntimeErrorException(message);
                e.AddLocation(d.Context);
                throw e;
            }
            Argument arg = d.AsDottedNameCall.Arguments.Items[0];
            Expression n = arg.AsPositional.Value;
            value = n.GetValue<T>();
            return true;
        }

        public T GetSingleValue<T>(string key)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));
            Decorator d = GetSingle(key);
            if (!IsSingleValued(d))
            {
                string message = string.Format(SR.DecoratorNotSingleValued, key.AsSingleQuote());
                RuntimeErrorException e = new RuntimeErrorException(message);
                e.AddLocation(d.Context);
                throw e;
            }
            Argument arg = d.AsDottedNameCall.Arguments.Items[0];
            Expression n = arg.AsPositional.Value;
            T value = n.GetValue<T>();
            return value;
        }
    }
}
